package com.algo.Jaya;

import java.util.Arrays;
import java.util.Random;

/**
 * @Author: guoxing
 * @Email: gx_study2022@163.com
 * @Description: https://blog.csdn.net/qq_37853160/article/details/127273019
 * @Date: 2024/6/24 22:26
 * @ClassName: Individual
 **/
public class Individual {

    private int[] machineCode;
    private int[] processCode;
    private int codeLength;


    private int[][] processTime;
    private int[][][] processDetailTime;
    private int[] machineTime;

    private RawData rawData;

    private int[] processNum;


    private int finishTime = -1;
    private int freeTime = 0;



    /**
     * 构造函数，同时完成各个字段以及个体基因序列的初始化
     * @param rawData
     */
    public Individual(RawData rawData){
        this.rawData = rawData;
        this.codeLength = this.rawData.getTotalProcessNum();
        this.machineCode = new int[this.codeLength];
        this.processCode = new int[this.codeLength];
        this.processTime = new int[this.rawData.getJobNum()][];
        this.processDetailTime = new int[this.rawData.getJobNum()][][];
        this.machineTime = new int[this.rawData.getMachineNum()];
        this.processNum = new int[rawData.getJobNum()];
        // 初始化基因序列
        processCodeInit();
        machineCodeInit();
        arrInit();
        fillTime();
    }

    /**
     * 构造函数
     * @param machineCode
     * @param processCode
     */
    public Individual(int[] machineCode, int[] processCode, RawData rawData){
        this.rawData = rawData;
        this.codeLength = this.rawData.getTotalProcessNum();
        this.processTime = new int[this.rawData.getJobNum()][];
        this.processDetailTime = new int[this.rawData.getJobNum()][][];
        this.machineTime = new int[this.rawData.getMachineNum()];
        this.processNum = new int[this.rawData.getJobNum()];
        this.machineCode = machineCode;
        this.processCode = processCode;
        arrInit();
        fillTime();
    }

    public int getCodeLength() {
        return codeLength;
    }

    public RawData getRawData() {
        return rawData;
    }

    public int getFreeTime() {
        return freeTime;
    }

    public int getFinishTime() {
        if(this.finishTime==-1){
            setFinishTime();
        }
        return this.finishTime;
    }

    private void setFinishTime() {
        int max = this.machineTime[0];
        for (int i : machineTime) {
            max = i>max?i:max;
        }
        this.finishTime = max;
    }

    public int[] getMachineCode() {
        return machineCode;
    }

    public int[] getProcessCode() {
        return processCode;
    }

    public void setMachineCode(int[] machineCode) {
        this.machineCode = machineCode;
    }

    public void setProcessCode(int[] processCode) {
        this.processCode = processCode;
    }

    public void setGene(int type, int offset, int gene){
        if(type == 0){
            this.machineCode[offset] = gene;
        }
        if(type == 1){
            this.processCode[offset] = gene;
        }
    }

    public int getGene(int type, int offset){
        if(type == 0) {
            return this.machineCode[offset];
        }
        if(type == 1) {
            return this.processCode[offset];
        }
        return -1;
    }
    /**
     * 支配关系判断
     * @param individual
     * @return
     */
    public boolean dominate(Individual individual){
        return (this.getFinishTime()<= individual.getFinishTime() && this.getFreeTime()<=individual.getFreeTime())
                && ((this.getFinishTime()!= individual.getFinishTime()) || (this.getFreeTime()!=individual.getFreeTime()));
    }
    /**
     * 填充机器时间和工序时间两个数组
     */
    private void fillTime(){
        int[][][][] data = this.rawData.getData();
        int[][] machineCode1 = convertMachineCode();
        int[] processAdjust = new int[machineCode1.length];

        for (int i = 0; i < this.processCode.length; i++) {
            int job = this.processCode[i]-1;//工件号,下标0开始 2
            int process = processAdjust[job]++;//第几道工序，下标0开始 0
            int machine = data[job][process][machineCode1[job][process]-1][0]-1; //机器编号 下标从0开始 1
            int doTime = data[job][process][machineCode1[job][process]-1][1]; // 该工序的加工时间 6

            int[] ints = new int[3];// 0表示当前工序的开始时间，1表示当前工序的结束时间,2表示机器选择
            ints[2] = machine+1;
            if(process==0){
                // 无工序紧前的关系，直接考虑当前机器
                ints[0] = this.machineTime[machine];  this.machineTime[machine] += doTime;
                ints[1] = this.machineTime[machine];
                this.processTime[job][process] = this.machineTime[machine];
                this.processDetailTime[job][process] = ints;
            }else{
                int rightTime = Math.max(this.machineTime[machine],this.processTime[job][process-1]);
                ints[0] = rightTime;
                this.freeTime += this.machineTime[machine]<this.processTime[job][process-1]?
                        (this.processTime[job][process-1]-this.machineTime[machine]) :0;

                this.machineTime[machine] =rightTime+doTime;
                ints[1] = this.machineTime[machine];
                this.processTime[job][process] = this.machineTime[machine];
                this.processDetailTime[job][process] = ints;
            }
        }
        int ft = getFinishTime();
        for (int i = 0; i < machineTime.length; i++) {
            this.freeTime += ft-machineTime[i];
        }
    }

    /**
     * 将机器码转换为二维形式，方便解码
     * @return
     */
    private int[][] convertMachineCode(){
        int[][] res = new int[this.rawData.getJobNum()][];

        for(int i=0;i<res.length;i++){
            res[i] = new int[this.processNum[i]];
        }
        int index1 = 0;
        int index2 = 0;
        for (int i = 0; i < this.machineCode.length; i++) {
            res[index1][index2] = this.machineCode[i];
            index2++;
            if(index2>=this.processNum[index1]){
                index1++;
                index2 = 0;
            }
        }
        return res;
    }

    /**
     * 机器编码初始化，包含启发式初始化（20%概率）和随机初始化（80概率）
     */
    private void machineCodeInit(){
        int[][][][] data = this.rawData.getData();
        int[] genes = new int[this.codeLength];
        double pointer = Math.random();
        Random rnd = new Random();
        int index = 0;
        if(pointer<0.2){
            // 启发式方法
            int[] machineTime = new int[this.rawData.getMachineNum()];
            for(int i= 1;i<=this.rawData.getJobNum();i++){
                for(int j=0;j<data[i-1].length;j++){
                    int[] t = new int[this.rawData.getMachineNum()];
                    int[][] optionalMachine = data[i-1][j];
                    for (int i1 = 0; i1 < optionalMachine.length; i1++) {
                        t[optionalMachine[i1][0]-1] = machineTime[optionalMachine[i1][0]-1]+optionalMachine[i1][1];

                    }

                    int minIndex = 0;
                    for(int a = 0;a<t.length;a++){
                        if(t[a]!=0){
                            if(t[minIndex]==0) {
                                minIndex = a;
                                continue;
                            }
                            minIndex = t[minIndex]<=t[a]?minIndex:a;
                        }
                    }

                    machineTime[minIndex] = t[minIndex];
                    int gene = 0;
                    for(int a=0;a< optionalMachine.length;a++){
                        if(optionalMachine[a][0]==minIndex+1){
                            gene = a+1;
                            break;
                        }
                    }
                    genes[index] = gene;
                    index++;
                }
            }
        }else{
            // 随机方法
            for(int i= 1;i<=this.rawData.getJobNum();i++){
                for(int j=0;j<data[i-1].length;j++){
                    //genes[index] = rnd.nextInt(optionalMachine[i-1][j].length+1);
                    genes[index] = rnd.nextInt(data[i-1][j].length)+1;
                    index++;
                }
            }
        }
        this.machineCode = genes;
    }

    /**
     * 工序编码初始化，采用随机方法
     */
    private void processCodeInit(){
        int length = this.codeLength;
        int[] genes = new int[length];
        Random rnd = new Random();
        int[] indexes = new int[length];

        for(int i=0;i<length;i++){
            indexes[i] = i;
        }

        int right = length;
        for(int i=1;i<=this.rawData.getJobNum();i++){
            for(int j=0;j<this.rawData.getData()[i-1].length;j++){
                int rndIndex = rnd.nextInt(right);
                genes[indexes[rndIndex]] = i;

                int t = indexes[right-1];
                indexes[right-1] = indexes[rndIndex];
                indexes[rndIndex] = t;
                right--;
            }
        }
        this.processCode = genes;
    }

    /**
     * 初始化几个数组，防止出现空指针
     */
    private void arrInit(){
        for (int i = 0; i < this.rawData.getJobNum(); i++) {
            this.processNum[i] = this.rawData.getData()[i].length;// 填充工序数量的数组，主要是为解码做准备
            this.processTime[i] = new int[this.rawData.getData()[i].length];// 初始化数组
            this.processDetailTime[i] = new int[this.rawData.getData()[i].length][];
        }
    }

    @Override
    public String toString() {
        return "Individual{machineCode="+Arrays.toString(this.machineCode)+";processCode="+ Arrays.toString(this.processCode)+"}";
    }

    public void printDetailTime(){
        int[][][] detailTime = this.processDetailTime;

        for (int i = 0; i < detailTime.length; i++) {
            System.out.println("=========================");
            System.out.println("第"+(i+1)+"个工件");
            for (int j = 0; j < detailTime[i].length; j++) {
                System.out.println("第"+(j+1)+"个工序：开始时间为"+detailTime[i][j][0]
                        +"；结束时间为"+detailTime[i][j][1]+";选择的机器为"+detailTime[i][j][2]);
            }
        }
    }

    public int[][][] decode(){
        return this.processDetailTime;
    }
}


